package com.tmt.fileservice.impl;

import com.tmt.fileservice.IFileCache;
import com.tmt.fileservice.IFileDataResolver;
import com.tmt.fileservice.IFileStorage;
import com.tmt.fileservice.IFileValidator;
import com.tmt.fileservice.exception.FileLoadException;
import com.tmt.fileservice.exception.FileValidateException;
import com.tmt.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by ThreeManTeam on 2017/5/25.
 */
public class DefaultFileStorage implements IFileStorage {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private Map<String, IFileValidator> fileValidatorMap;

    private IFileCache fileCache;

    private static DefaultFileStorage instance;

    private DefaultFileStorage(Map<String, IFileValidator> fileValidatorMap) {
        this.fileValidatorMap = fileValidatorMap;
    }

    public synchronized static DefaultFileStorage getInstance(Map<String, IFileValidator> fileValidatorMap) {
        if (instance == null)
            instance = new DefaultFileStorage(fileValidatorMap);
        return instance;
    }

    @Override
    public void setFileCache(IFileCache fileCache) {
        this.fileCache = fileCache;
    }


    @Override
    public void save(String filePath, IFileDataResolver resolver, String validatorName) throws FileValidateException {
        checkFileTypeAndLength(validatorName, resolver.getFileType(), resolver.getFileLength());
        try {
            File file = new File(filePath);
            if (!file.getParentFile().exists())
                file.getParentFile().mkdirs();

            if (!file.exists())
                file.createNewFile();

            resolver.transferTo(file);
            try {
                checkFileProperty(validatorName, file);
            } catch (Exception ex) {
                file.delete();
                throw ex;
            }
        } catch (Exception ex) {
            throw new FileValidateException(MessageFormat.format("保存文件异常：{0}", ex.getMessage()));
        }
    }

    @Override
    public void load(String filePath, OutputStream out) throws FileLoadException {
        try {
            if (fileCache != null) {
                fileCache.load(filePath, out);
                return;
            }
        } catch (Exception ex) {
            logger.debug(MessageFormat.format("通过缓存系统加载文件{0}失败[{1}],已转向从本地文件系统加载目标文件", filePath, ex.getMessage()));
        }

        File file = new File(filePath);
        if (!file.exists())
            throw new FileLoadException(MessageFormat.format("要加载的本地文件{0}不存在", filePath));

        try (InputStream in = new FileInputStream(file)) {
            transferData(in, out);
        } catch (Exception ex) {
            throw new FileLoadException(MessageFormat.format("加载本地文件{0}异常：{1}", filePath, ex.getMessage()));
        }
    }

    @Override
    public void delete(String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            file.delete();
        }

        if (fileCache != null) {
            fileCache.delete(filePath);
        }
    }

    @Override
    public long getFileSize(String filePath) {
        try {
            if (fileCache != null) {
                return fileCache.getFileSize(filePath);
            }
        } catch (Exception ex) {
        }

        File file = new File(filePath);
        if (file.exists()) {
            return file.length();
        }
        return 0L;
    }

    @Override
    public long load(String filePath, long startPos, byte[] data) throws FileLoadException {
        try {
            if (fileCache != null) {
                return fileCache.load(filePath, startPos, data);
            }
        } catch (Exception ex) {
            logger.debug(MessageFormat.format("通过缓存系统加载文件{0}失败[{1}],已转向从本地文件系统加载目标文件", filePath, ex.getMessage()));
        }

        File file = new File(filePath);
        if (!file.exists())
            throw new FileLoadException(MessageFormat.format("要加载的本地文件{0}不存在", filePath));

        try (InputStream in = new FileInputStream(file)) {
            return transferData(in, startPos, data);
        } catch (Exception ex) {
            throw new FileLoadException(MessageFormat.format("加载本地文件{0}异常：{1}", filePath, ex.getMessage()));
        }
    }

    private void checkFileTypeAndLength(String validatorName, String fileType, Long fileLength) throws FileValidateException {
        List<IFileValidator> list = getFileValidator(validatorName);
        for (IFileValidator validator : list) {
            if (!validator.checkFileTypeAndLength(fileType, fileLength))
                break;
        }
    }

    private void checkFileProperty(String validatorName, File file) throws FileValidateException {
        List<IFileValidator> list = getFileValidator(validatorName);
        for (IFileValidator validator : list) {
            if (!validator.checkFileProperty(file))
                break;
        }
    }

    private List<IFileValidator> getFileValidator(String validatorName) {
        List<IFileValidator> list = new ArrayList<>();

        if (fileValidatorMap != null) {
            IFileValidator validator = fileValidatorMap.get(validatorName);
            if (validator != null)
                list.add(validator);

            String clsName = StringHelper.splitWithoutEmpty(validatorName, "#")[0];
            validator = fileValidatorMap.get(clsName);
            if (validator != null)
                list.add(validator);

            validator = fileValidatorMap.get("default");
            if (validator != null)
                list.add(validator);
        }

        return list;
    }


    private void transferData(InputStream in, OutputStream out) throws IOException {
        byte[] bytes = new byte[1024];
        int len = -1;
        while ((len = in.read(bytes)) != -1) {
            out.write(bytes, 0, len);
        }
    }

    private long transferData(InputStream in, long startPos, byte[] data) throws IOException {
        in.skip(startPos);
        return in.read(data, 0, data.length);
    }

}
